﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using Ams.Extensions;
using Ams.Mvc.Bootstrap.Annotations;

namespace Ams.Mvc.Bootstrap
{
    public static class BootstrapEditorExtensions
    {
        #region EditorFor
        public static MvcHtmlString EditorFor<TModel, TValue>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
        {
            return EditorFor(helper, expression, null);
        }
        public static MvcHtmlString EditorFor<TModel, TValue>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, object additionalViewData)
        {
            return EditorFor(helper, expression, null, additionalViewData);
        }
        public static MvcHtmlString EditorFor<TModel, TValue>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string templateName)
        {
            return EditorFor(helper, expression, templateName, null);
        }
        public static MvcHtmlString EditorFor<TModel, TValue>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string templateName, object additionalViewData)
        {
            if (GetDataType(expression) == DataType.Html) 
                return helper.RichTextFor(expression);
            
            if (GetDataType(expression) == DataType.MultilineText) 
                return helper.TextAreaFor(expression);
            
            if (expression.GetPropertyInfo().Attribute<DropDownAttribute>() != null || typeof (TValue).IsEnum)
                return helper.DropDownFor(expression);

            if (expression.GetPropertyInfo().PropertyType == typeof(bool))
                return helper.CheckBoxFor(expression);
            
            //Nothing special
            var html = helper.Html.EditorFor(expression).ToString();
            HtmlMod.AddClass(ref html, "form-control");
            AddExtraClassesIfNeeded(ref html, expression);

            if (helper.Form != null && helper.Form.FormType == Form.Horizontal)
            {
                var divWrapper = new TagBuilder("div");
                divWrapper.AddCssClass(helper.Form.EditorClasses);
                divWrapper.InnerHtml = html;
                html = divWrapper.ToString();
            }

            return new MvcHtmlString(html);
        }
        #endregion

        public static MvcHtmlString LabeledEditorFor<TModel, TValue>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
        {
            var editor = helper.EditorFor(expression);

            if (editor.ToString().Matches("class=\"checkbox\""))
            {
                return editor;
            }

            var label = helper.LabelFor(expression);
            var validation = helper.ValidationMessageFor(expression);

            var formGroup = new TagBuilder("div");
            formGroup.AddCssClass("form-group");
            formGroup.InnerHtml = new[] {
                label,
                editor,
                validation,
            }.Join(Environment.NewLine);

            return formGroup.ToMvcHtmlString();
        }

        public static MvcHtmlString EditorForModel<TModel>(this BootstrapHelper<TModel> helper)
        {
            var model = helper.ViewData.Model;
            var modelType = model.GetType();
            var modelProps = modelType.GetProperties(BindingFlags.Instance | BindingFlags.Public);

            var html = new List<string>();

            var parameterExpression = Expression.Parameter(modelType, "x");

            foreach (var prop in modelProps)
            {
                var propertyAccess = Expression.MakeMemberAccess(parameterExpression, prop);

                var lambdaExpression = typeof(Expression).GetMethods().Where(i => i.Name == "Lambda" && i.GetParameters().Count() == 2).ToArray()[0];
                var funcType = typeof(Func<,>).MakeGenericType(new[] { modelType, prop.PropertyType });
                var lambdaGeneric = lambdaExpression.MakeGenericMethod(new[] { funcType });
                var expression = lambdaGeneric.Invoke(null, new object[] { propertyAccess, new[] { parameterExpression } });

                var m = typeof(BootstrapEditorExtensions).GetMethod("LabeledEditorFor").MakeGenericMethod(new[] { modelType, prop.PropertyType });
                
                var itemHtml = m.Invoke(null, new object[] { helper, expression }).ToString();
                html.Add(itemHtml);
            }

            return new MvcHtmlString(String.Join(Environment.NewLine, html));
        }

        #region LabeledFileUpload
        public static MvcHtmlString LabeledFileUpload(this BootstrapHelper helper, string fieldName)
        {
            return LabeledFileUpload(helper, fieldName, fieldName.Titlize());
        }
        public static MvcHtmlString LabeledFileUpload(this BootstrapHelper helper, string fieldName, string label)
        {
            return LabeledFileUpload(helper, fieldName, label, label);
        }
        public static MvcHtmlString LabeledFileUpload(this BootstrapHelper helper, string fieldName, string label, string placeHolder)
        {
            var id = HtmlHelper.GenerateIdFromName(fieldName);

            var labelAttribute = HtmlHelper.AnonymousObjectToHtmlAttributes(new
            {
                @class = "control-label",
                @for = id
            });

            var labelTag = new TagBuilder("label");
            labelTag.SetInnerText(label);
            labelTag.MergeAttributes(labelAttribute);
            
            var inputAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(new
            {
                type = "file",
                @class = "form-control",
                id = id,
                name = fieldName,
                placeholder = placeHolder
            });
            var editor = new TagBuilder("input");
            editor.MergeAttributes(inputAttributes);
            
            if (helper.Form != null)
            {
                if (helper.Form.FormType == Form.Horizontal)
                {
                    editor = new TagBuilder("div")
                    {
                        InnerHtml = editor.ToString()
                    };
                }

                labelTag.MergeAttribute("class", helper.Form.LabelClasses);
                editor.MergeAttribute("class", helper.Form.EditorClasses);
            }

            var formGroup = new TagBuilder("div");
            formGroup.AddCssClass("form-group");
            formGroup.InnerHtml = new[] {
                labelTag.ToString(),
                editor.ToString()
            }.Join(Environment.NewLine);

            return formGroup.ToMvcHtmlString();
        } 
        #endregion

        private static DataType GetDataType<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
        {
            var prop = expression.GetPropertyInfo();
            if (prop == null) return DataType.Text;

            var dataTypeAttr = prop.Attribute<DataTypeAttribute>();
            if (dataTypeAttr == null) return DataType.Text;

            return dataTypeAttr.DataType;
        }
        private static void AddExtraClassesIfNeeded<TModel, TValue>(ref string html, Expression<Func<TModel, TValue>> expression)
        {
            var dataType = GetDataType(expression);
            switch (dataType)
            {
                case DataType.DateTime:
                    HtmlMod.AddClass(ref html, "input-datetime");
                    break;
                case DataType.Date:
                    HtmlMod.AddClass(ref html, "input-date");
                    break;
                case DataType.Time:
                    HtmlMod.AddClass(ref html, "input-time");
                    break;
                case DataType.Duration:
                    HtmlMod.AddClass(ref html, "input-timespan");
                    break;
                case DataType.PhoneNumber:
                    HtmlMod.AddClass(ref html, "input-phone");
                    break;
                case DataType.Currency:
                    HtmlMod.AddClass(ref html, "input-money");
                    break;
                case DataType.Html:
                    HtmlMod.AddClass(ref html, "input-html");
                    break;
                case DataType.MultilineText:
                    HtmlMod.AddClass(ref html, "input-multiline");
                    break;
                case DataType.EmailAddress:
                    HtmlMod.AddClass(ref html, "input-email");
                    break;
                case DataType.Url:
                    HtmlMod.AddClass(ref html, "input-url");
                    break;
                case DataType.ImageUrl:
                    HtmlMod.AddClass(ref html, "input-img-url");
                    break;
            }
        }
    }
}
